package com.mdd.admin.service.impl;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.io.resource.ResourceUtil;
import cn.hutool.core.lang.UUID;
import cn.hutool.core.util.CharsetUtil;
import cn.hutool.extra.validation.ValidationUtil;
import com.alibaba.excel.EasyExcel;
import com.alibaba.fastjson.JSON;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.core.toolkit.IdWorker;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.core.toolkit.support.SFunction;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.github.yulichang.query.MPJQueryWrapper;
import com.mdd.admin.service.ICompetitionApplyService;
import com.mdd.admin.validate.commons.PageValidate;
import com.mdd.admin.validate.competition.*;
import com.mdd.admin.validate.member.MemberApplyValidate;
import com.mdd.admin.validate.member.MemberExamineValicate;
import com.mdd.admin.vo.competition.*;
import com.mdd.common.core.AjaxResult;
import com.mdd.common.core.PageResult;
import com.mdd.common.entity.competition.Competition;
import com.mdd.common.entity.competition.CompetitionApply;
import com.mdd.common.entity.member.Member;
import com.mdd.common.exception.OperateException;
import com.mdd.common.mapper.competition.CompetitionApplyMapper;
import com.mdd.common.mapper.competition.CompetitionMapper;
import com.mdd.common.mapper.member.MemberMapper;
import com.mdd.common.util.FileUploadUtils;
import com.mdd.common.util.RedisUtils;
import com.mdd.common.util.StringUtils;
import com.mdd.common.util.TimeUtils;
import lombok.extern.slf4j.Slf4j;
import org.apache.http.entity.ContentType;
import org.springframework.beans.BeanUtils;
import org.springframework.stereotype.Service;
import org.springframework.util.Assert;
import org.springframework.web.multipart.MultipartFile;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.*;
import java.util.stream.Collectors;

/**
 * 赛事申请实现类
 * @author LikeAdmin
 */
@Slf4j
@Service
public class CompetitionApplyServiceImpl implements ICompetitionApplyService {

    @Resource
    CompetitionApplyMapper competitionApplyMapper;

    @Resource
    CompetitionMapper competitionMapper;

    @Resource
    MemberMapper memberMapper;

    /**
     * 赛事申请列表
     *
     * @author LikeAdmin
     * @param pageValidate 分页参数
     * @param searchValidate 搜索参数
     * @return PageResult<CompetitionApplyListedVo>
     */
    @Override
    public PageResult<CompetitionApplyListedVo> list(PageValidate pageValidate, CompetitionApplySearchValidate searchValidate) {
        Integer page  = pageValidate.getPage_no();
        Integer limit = pageValidate.getPage_size();
        if(searchValidate.getSchedule()==null) {
            Competition competition = competitionMapper.selectById(searchValidate.getCompetitionId());
            searchValidate.setSchedule(Integer.parseInt(competition.getSchedule()));
        }
        String lianjie = "inner";
        if(searchValidate.getSchedule()==1){
            lianjie = "left";
        }
        MPJQueryWrapper<CompetitionApply> queryWrapper = new MPJQueryWrapper<>();
        queryWrapper.selectAll(CompetitionApply.class)
                .select("t2.name AS realName," +
                        "    t2.sheng_num," +
                        "    t2.bai_num," +
                        "    t2.jifen")
                .innerJoin(
                        "(select " +
                                "    t.id," +
                                "    t.name," +
                                "    t3.status," +
                                "    t3.match_id," +
                                "    SUM(CASE" +
                                "        WHEN t3.status = 1 THEN 1" +
                                "        ELSE 0" +
                                "    END) AS sheng_num," +
                                "    SUM(CASE" +
                                "        WHEN t3.status = 2 THEN 1" +
                                "        ELSE 0" +
                                "    END) AS bai_num," +
                                "    SUM(CASE" +
                                "        WHEN t3.status = 1 THEN 1" +
                                "        ELSE 0" +
                                "    END) * 3 AS jifen" +
                                "    from la_member t" +
                                "    inner join la_competition_apply t1 on t1.member_id = t.id " +
//                                "    left join la_competition_match_team t2 on t1.team_no = t2.team_no" +
//                                "    "+lianjie+" JOIN la_competition_match t3 ON (t3.id = t2.match_id or t3.result = t1.team_no) AND t3.schedule ="+searchValidate.getSchedule() +
                                "    "+lianjie+" join (select " +
                                "        t2.team_no," +
                                "        t2.status," +
                                "        t2.match_id" +
                                "    from la_competition_match_team t2 " +
                                "    INNER JOIN la_competition_match t3 ON t3.id = t2.match_id AND t3.schedule = 2 and t3.competition_id = "+searchValidate.getCompetitionId() +
                                "    union" +
                                "    select " +
                                "        t4.result as team_no," +
                                "        0 as status," +
                                "        t4.id as match_id" +
                                "    from la_competition_match t4 where t4.schedule = 2 and t4.status = 1 and  t4.competition_id = "+searchValidate.getCompetitionId() +

                                "    ) t3 on t1.team_no = t3.team_no"+

                                "    where t1.competition_id ="+searchValidate.getCompetitionId() +
                                "    group by id"+
                                ") t2 on t2.id = t.member_id"
                )
                .groupBy("t.team_no,t.member_id")
                .orderByDesc("t2.jifen")
                .orderByDesc("id");
        competitionApplyMapper.setSearch(queryWrapper, searchValidate, new String[]{
                "=:competitionId@t.competition_id:int",
                "like:competitionName@lc.name:str",
                "like:teamName@lt.name:str",
                "=:createId@create_id:int",
        });

        IPage<CompetitionApplyListedVo> iPage = competitionApplyMapper.selectJoinPage(new Page<>(page, limit),
                CompetitionApplyListedVo.class,
                queryWrapper);

        List<CompetitionApplyListedVo> list = new LinkedList<>();
        for(CompetitionApplyListedVo item : iPage.getRecords()) {
            item.setCreateTime(TimeUtils.timestampToDate(item.getCreateTime()));
            list.add(item);
        }

        return PageResult.iPageHandle(iPage.getTotal(), iPage.getCurrent(), iPage.getSize(), list);
    }

    /**
     * 赛事申请详情
     *
     * @author LikeAdmin
     * @param id 主键参数
     * @return CompetitionApply
     */
    @Override
    public CompetitionApplyDetailVo detail(Integer id) {
        CompetitionApply model = competitionApplyMapper.selectOne(
                new QueryWrapper<CompetitionApply>()
                        .eq("id", id)
                        .last("limit 1"));

        Assert.notNull(model, "数据不存在");

        CompetitionApplyDetailVo vo = new CompetitionApplyDetailVo();
        BeanUtils.copyProperties(model, vo);
        return vo;
    }

    /**
     * 赛事申请新增
     *
     * @author LikeAdmin
     * @param createValidate 参数
     */
    @Override
    public void add(CompetitionApplyCreateValidate createValidate) {
        CompetitionApply model = new CompetitionApply();
        model.setCompetitionId(createValidate.getCompetitionId());
        model.setTeamNo(createValidate.getTeamNo());
        model.setStatus(createValidate.getStatus());
        model.setCreateId(createValidate.getCreateId());
        model.setCreateTime(System.currentTimeMillis() / 1000);
        competitionApplyMapper.insert(model);
    }

    /**
     * 赛事申请编辑
     *
     * @author LikeAdmin
     * @param updateValidate 参数
     */
    @Override
    public void edit(CompetitionApplyUpdateValidate updateValidate) {
        CompetitionApply model = competitionApplyMapper.selectOne(
                new QueryWrapper<CompetitionApply>()
                        .eq("id",  updateValidate.getId())
                        .last("limit 1"));

        Assert.notNull(model, "数据不存在!");

        model.setId(updateValidate.getId());
        model.setCompetitionId(updateValidate.getCompetitionId());
        model.setTeamNo(updateValidate.getTeamNo());
        model.setStatus(updateValidate.getStatus());
        model.setCreateId(updateValidate.getCreateId());
        competitionApplyMapper.updateById(model);
    }

    /**
     * 赛事申请删除
     *
     * @author LikeAdmin
     * @param id 主键ID
     */
    @Override
    public void del(Integer id) {
        CompetitionApply model = competitionApplyMapper.selectOne(
                new QueryWrapper<CompetitionApply>()
                        .eq("id", id)
                        .last("limit 1"));

        Assert.notNull(model, "数据不存在!");

        competitionApplyMapper.delete(new QueryWrapper<CompetitionApply>().eq("id", id));
    }

    @Override
    public void downloadImportTemplate(HttpServletResponse response) throws IOException {
        try {
            FileUploadUtils.downloadExcel(response, ResourceUtil.getStream("templates/import/user.xlsx"), "报名导入模板.xlsx");
        } catch (Exception e) {
            log.error("下载报名导入模板失败：{}", e.getMessage(), e);
            response.setCharacterEncoding(CharsetUtil.UTF_8);
            response.setContentType(ContentType.APPLICATION_JSON.getMimeType());
            response.getWriter().write(JSON.toJSONString(AjaxResult.failed("下载报名导入模板失败")));
        }
    }

    @Override
    public CompetitionApplyImportParseVo parseImport(MultipartFile file) {
        CompetitionApplyImportParseVo userImportResp = new CompetitionApplyImportParseVo();
        List<CompetitionImportApplyValidate> importRowList;
        // 读取表格数据
        try {
            importRowList = EasyExcel.read(file.getInputStream())
                    .head(CompetitionImportApplyValidate.class)
                    .sheet()
                    .headRowNumber(1)
                    .doReadSync();
        } catch (Exception e) {
            log.error("用户导入数据文件解析异常：{}", e.getMessage(), e);
            throw new OperateException("数据文件解析异常");
        }
        // 总计行数
        userImportResp.setTotalRows(importRowList.size());
        if(CollUtil.isEmpty(importRowList)){
            throw new OperateException("数据文件格式错误");
        }
//        CheckUtils.throwIfEmpty(importRowList, "数据文件格式错误");
        // 有效行数：过滤无效数据
        List<CompetitionImportApplyValidate> validRowList = this.filterImportData(importRowList);
        userImportResp.setValidRows(validRowList.size());
        if(CollUtil.isEmpty(validRowList)){
            throw new OperateException("数据文件格式错误");
        }
//        CheckUtils.throwIfEmpty(validRowList, "数据文件格式错误");
        // 检测表格内数据是否合法
        Set<String> seenStudentNums = new HashSet<>();
        boolean hasDuplicateEmail = validRowList.stream()
                .map(CompetitionImportApplyValidate::getStudentNum)
                .anyMatch(email -> email != null && !seenStudentNums.add(email));
        if(hasDuplicateEmail){
            throw new OperateException("存在重复学号，请检测数据");
        }
//        CheckUtils.throwIf(hasDuplicateEmail, "存在重复邮箱，请检测数据");
        Set<String> seenPhones = new HashSet<>();
        boolean hasDuplicatePhone = validRowList.stream()
                .map(CompetitionImportApplyValidate::getContact)
                .anyMatch(phone -> phone != null && !seenPhones.add(phone));
        if(hasDuplicatePhone){
            throw new OperateException("存在重复手机，请检测数据");
        }
//        CheckUtils.throwIf(hasDuplicatePhone, "存在重复手机，请检测数据");

        // 校验是否存在错误角色
        List<String> competitionNames = validRowList.stream().map(CompetitionImportApplyValidate::getCompetitionName).distinct().collect(Collectors.toList());
        int existRoleCount = competitionMapper.selectCount(new LambdaUpdateWrapper<Competition>().in(Competition::getName,competitionNames)).intValue();
        if(existRoleCount<competitionNames.size()){
            throw new OperateException("存在错误赛事，请检查数据");
        }
//        CheckUtils.throwIf(existRoleCount < roleNames.size(), "存在错误角色，请检查数据");
        // 校验是否存在错误部门
//        List<String> deptNames = validRowList.stream().map(UserImportRowReq::getDeptName).distinct().toList();
//        int existDeptCount = deptService.countByNames(deptNames);
//        CheckUtils.throwIf(existDeptCount < deptNames.size(), "存在错误部门，请检查数据");
        List<String> studenNumList = validRowList.stream().map(CompetitionImportApplyValidate::getStudentNum).filter(Objects::nonNull).collect(Collectors.toList());
        List<String> contactList = validRowList.stream().map(CompetitionImportApplyValidate::getContact).filter(Objects::nonNull).collect(Collectors.toList());
        // 查询重复用户
        userImportResp
                .setDuplicateUserRows(
                        memberMapper.selectCount(new LambdaUpdateWrapper<Member>().in(Member::getStudentNum,studenNumList)).intValue());

//                        countExistByField(validRowList, UserImportRowReq::getUsername, UserDO::getUsername, false));
        // 查询重复邮箱
//        userImportResp
//                .setDuplicateEmailRows(countExistByField(validRowList, UserImportRowReq::getEmail, UserDO::getEmail, true));
        // 查询重复手机
        userImportResp
                .setDuplicatePhoneRows(memberMapper.selectCount(new LambdaUpdateWrapper<Member>()
                        .in(Member::getContact, contactList)).intValue());

        // 设置导入会话并缓存数据，有效期10分钟
        String importKey = UUID.fastUUID().toString(true);
        RedisUtils.set("competition_apply:import:" + importKey, JSON.toJSONString(validRowList), 300);
        userImportResp.setImportKey(importKey);
        return userImportResp;
    }

    private List<CompetitionImportApplyValidate> filterImportData(List<CompetitionImportApplyValidate> importRowList) {
        // 校验过滤
        List<CompetitionImportApplyValidate> list = importRowList.stream()
                .filter(row -> ValidationUtil.validate(row).isEmpty()).collect(Collectors.toList());
        // 用户名去重
        return list.stream()
                .collect(Collectors.toMap(CompetitionImportApplyValidate::getName, user -> user, (existing, replacement) -> existing))
                .values()
                .stream()
                .collect(Collectors.toList());
    }

    @Override
    public CompetitionApplyImportVo importUser(CompetitionApplyImportValidate req) {
        // 校验导入会话是否过期
        List<CompetitionImportApplyValidate> importUserList;
        try {
            String data = (String) RedisUtils.get("competition_apply:import:"  + req.getImportKey());
            importUserList = JSON.parseArray(data, CompetitionImportApplyValidate.class);
            if(CollUtil.isEmpty(importUserList)){
                throw new OperateException("导入已过期，请重新上传");
            }
//            CheckUtils.throwIf(CollUtil.isEmpty(importUserList), "导入已过期，请重新上传");
        } catch (Exception e) {
            log.error("导入异常:", e);
            throw new OperateException("导入已过期，请重新上传");
        }
        List<String> existStudentNums = listExistByField(importUserList, CompetitionImportApplyValidate::getStudentNum, Member::getStudentNum);
        List<String> existPhones = listExistByField(importUserList, CompetitionImportApplyValidate::getContact, Member::getContact);
        boolean exitImportUser = isExitImportUser(req, importUserList, existStudentNums, existPhones);
        if(exitImportUser){
            throw new OperateException("数据不符合导入策略，已退出导入");
        }
        Map<String,String> teamNoMap = new HashMap<>();
        int insertNum = 0;
        for (CompetitionImportApplyValidate row : importUserList) {
            if (isSkipUserImport(req, row, existStudentNums, existPhones)) {
                // 按规则跳过该行
                continue;
            }
            Member userDO = BeanUtil.toBeanIgnoreError(row, Member.class);
            userDO.setStatus(1);
            userDO.setType(3);
            userDO.setRoleCode("member");
            userDO.setRoleName("社团成员");
            if("男".equals(row.getSex())){
                userDO.setSex(1);
            }else if("女".equals(row.getSex())){
                userDO.setSex(0);
            }else {
                throw new OperateException("导入数据，性别有问题!");
            }
            memberMapper.insert(userDO);
            CompetitionApply apply = new CompetitionApply();
            apply.setCompetitionId(req.getCompetitionId());
            apply.setMemberId(userDO.getId());
            if("男子单打".equals(row.getType())){
                apply.setApplyType(1);
            }else if("男子双打".equals(row.getType())){
                apply.setApplyType(3);
            }else if("女子单打".equals(row.getType())){
                apply.setApplyType(2);
            }else if("女子双打".equals(row.getType())){
                apply.setApplyType(4);
            }else if("男女混打".equals(row.getType())){
                apply.setApplyType(5);
            }else {
                throw new OperateException("导入类型错误");
            }
            apply.setStatus(0);
            String s = teamNoMap.get(row.getTeamNo());
            if(StringUtils.isNotEmpty(s)){
                apply.setTeamNo(s);
            }else{
                String idStr = IdWorker.getIdStr();
                apply.setTeamNo(idStr);
                teamNoMap.put(row.getTeamNo(),idStr);
            }
            competitionApplyMapper.insert(apply);
            insertNum++;
        }
        return new CompetitionApplyImportVo(importUserList.size() , insertNum);
    }

    private boolean isExitImportUser(CompetitionApplyImportValidate req,
                                     List<CompetitionImportApplyValidate> list,
                                     List<String> existUsernames,
                                     List<String> existPhones) {
        return list.stream()
                .anyMatch(row ->
                {
                    if((req.getDuplicateUser()==2&& CollUtil.isNotEmpty(existUsernames) && existUsernames.contains(row.getStudentNum()))||
                            (req.getDuplicatePhone()==2&& CollUtil.isNotEmpty(existPhones) && existPhones.contains(row.getContact()))
                    ){
                        return true;
                    }else{
                        return false;
                    }
                });
    }

    private boolean isSkipUserImport(CompetitionApplyImportValidate req,
                                     CompetitionImportApplyValidate row,
                                     List<String> existUsernames,
                                     List<String> existPhones) {
        if((req.getDuplicateUser()==1&& CollUtil.isNotEmpty(existUsernames) && existUsernames.contains(row.getStudentNum()))||
                (req.getDuplicatePhone()==1&& CollUtil.isNotEmpty(existPhones) && existPhones.contains(row.getContact()))
        ){
            return true;
        }else{
            return false;
        }
    }

    private List<String> listExistByField(List<CompetitionImportApplyValidate> userRowList,
                                          SFunction<CompetitionImportApplyValidate, String> rowField,
                                          SFunction<Member, String> dbField) {
        List<String> fieldValues = userRowList.stream().map(rowField).filter(Objects::nonNull).collect(Collectors.toList());
        if (fieldValues.isEmpty()) {
            return Collections.emptyList();
        }
        List<Member> userDOList = memberMapper.selectList(Wrappers.<Member>lambdaQuery()
                .in(dbField, fieldValues)
                .select(dbField));
        return userDOList.stream().map(dbField).filter(Objects::nonNull).collect(Collectors.toList());
    }

    @Override
    public void signUp(MemberApplyValidate memberApplyValidate) {
        List<Member> l = memberMapper.selectList(new LambdaUpdateWrapper<Member>().eq(Member::getStudentNum, memberApplyValidate.getStudentNum()));
        Member member = new Member();
        if(CollUtil.isNotEmpty(l)){
            member = l.get(0);
        }else{
//            Member member = new Member();
            BeanUtils.copyProperties(memberApplyValidate,member);
            member.setRoleName("社团成员");
            member.setRoleCode("member");
            member.setStatus(0);
            member.setType(3);
            memberMapper.insert(member);
        }
        boolean exists = competitionApplyMapper.exists(new LambdaQueryWrapper<CompetitionApply>()
                .eq(CompetitionApply::getCompetitionId, memberApplyValidate.getCompetitionId())
                .eq(CompetitionApply::getApplyType, memberApplyValidate.getPlayType())
                .eq(CompetitionApply::getMemberId,member.getId()));
        if(!exists) {
            CompetitionApply competitionApply = new CompetitionApply();
            competitionApply.setCompetitionId(memberApplyValidate.getCompetitionId());
            competitionApply.setMemberId(member.getId());
            competitionApply.setApplyType(memberApplyValidate.getPlayType());
            competitionApply.setStatus(0);
            competitionApply.setTeamNo(IdWorker.getIdStr());
            competitionApply.setCreateTime(System.currentTimeMillis() / 1000);
            competitionApplyMapper.insert(competitionApply);
        }
    }

    @Override
    public CompetitionApplyTeamVo team(String teamNo) {
        CompetitionApplyTeamVo member = new CompetitionApplyTeamVo();
        List<CompetitionApply> list = competitionApplyMapper.selectList(new LambdaUpdateWrapper<CompetitionApply>().eq(CompetitionApply::getTeamNo,teamNo));
        BeanUtils.copyProperties(list.get(0),member);
        List<Integer> collect = list.stream().map(m -> m.getMemberId()).collect(Collectors.toList());
        List<Member> members = memberMapper.selectList(new LambdaUpdateWrapper<Member>().in(Member::getId, collect));
        member.setMemberList(members);
        return member;
    }

    @Override
    public void memberExamine(MemberExamineValicate valicate) {
        CompetitionApplyTeamVo member = new CompetitionApplyTeamVo();
        member.setId(valicate.getId());
        member.setStatus(valicate.getStatus());
        competitionApplyMapper.updateById(member);
    }
}
